﻿using System;
using System.Collections.Generic;
using System.Linq;
using Properties;

namespace BorgExt
{
    public class InstanceStore : RuntimeElement, IInstanceStoreReader
    {
        private readonly Dictionary<object, Extension> _extensionObjectDictionary;
        private readonly Dictionary<string, MetaElementCollection<Extension>> _extensionDictionary;
        private readonly Dictionary<string, MetaElementCollection<Plug>> _plugDictionary;
        private readonly Dictionary<string, MetaElementCollection<Slot>> _slotDictionary;

        protected internal InstanceStoreModifier Modifier { get; protected set; }

        public IEnumerable<Extension> Extensions
        {
            get
            {
                lock (LockObject)
                    return TypeStore.ExtensionTypes.
                        SelectMany(extensionType => (IEnumerable<Extension>)extensionType.extensions);
            }
        }

        public IEnumerable<Plug> Plugs
        {
            get
            {
                lock (LockObject)
                    return Extensions.
                        SelectMany(extension => (IEnumerable<Plug>)extension.Plugs);
            }
        }

        public IEnumerable<Slot> Slots
        {
            get
            {
                lock (LockObject)
                    return Extensions.
                        SelectMany(extension => (IEnumerable<Slot>)extension.Slots);
            }
        }

        protected internal InstanceStore()
        {
            _extensionDictionary = new Dictionary<string, MetaElementCollection<Extension>>();
            _extensionObjectDictionary = new Dictionary<object, Extension>();
            _plugDictionary = new Dictionary<string, MetaElementCollection<Plug>>();
            _slotDictionary = new Dictionary<string, MetaElementCollection<Slot>>();
            Modifier = new InstanceStoreModifier(this);
        }

        public IEnumerable<Extension> GetExtensions(string name)
        {
            if (name == null)
                throw new ArgumentNullException("name");

            if (name == string.Empty)
                throw new ArgumentException(Messages.Name_must_not_be_empty);

            lock (LockObject)
                return InternalGetExtensions(name);
        }

        public IEnumerable<Plug> GetPlugs(string name)
        {
            if (name == null)
                throw new ArgumentNullException("name");

            if (name == string.Empty)
                throw new ArgumentException(Messages.Name_must_not_be_empty);

            lock (LockObject)
                return InternalGetPlugs(name);
        }

        public IEnumerable<Slot> GetSlots(string name)
        {
            if (name == null)
                throw new ArgumentNullException("name");

            if (name == string.Empty)
                throw new ArgumentException(Messages.Name_must_not_be_empty);

            lock (LockObject)
                return InternalGetSlots(name);
        }

        public MetaElementCollection<Slot> GetSlotsWherePlugged(Plug plug)
        {
            if (plug == null)
                throw new ArgumentNullException("plug");

            lock (LockObject)
                return InternalGetSlotsWherePlugged(plug);
        }

        public MetaElementCollection<Plug> GetPluggedPlugs(Slot slot)
        {
            if (slot == null)
                throw new ArgumentNullException("slot");

            lock (LockObject)
                return InternalGetPluggedPlugs(slot);
        }

        public MetaElementCollection<Slot> GetSlotsWhereSelected(Plug plug)
        {
            if (plug == null)
                throw new ArgumentNullException("plug");

            lock (LockObject)
                return InternalGetSlotsWhereSelected(plug);
        }

        public MetaElementCollection<Plug> GetSelectedPlugs(Slot slot)
        {
            if (slot == null)
                throw new ArgumentNullException("slot");

            lock (LockObject)
                return InternalGetSelectedPlugs(slot);
        }

        public MetaElementCollection<Extension> GetExtensions(ExtensionType extensionType)
        {
            if (extensionType == null)
                throw new ArgumentNullException("extensionType");

            lock (LockObject)
                return InternalGetExtensions(extensionType);
        }

        public Extension GetSharedExtension(ExtensionType extensionType)
        {
            if (extensionType == null)
                throw new ArgumentNullException("extensionType");

            lock (LockObject)
                return InternalGetSharedExtension(extensionType);
        }

        public Extension GetExtension(object extensionObject)
        {
            if (extensionObject == null)
                throw new ArgumentNullException("extensionObject");

            lock (LockObject)
                return InternalGetExtension(extensionObject);
        }

        public object GetExtensionObject(Extension extension)
        {
            if (extension == null)
                throw new ArgumentNullException("extension");

            lock (LockObject)
                return InternalGetExtensionObject(extension);
        }

        public Plug GetPlug(object extensionObject, string name)
        {
            if (extensionObject == null)
                throw new ArgumentNullException("extensionObject");

            if (name == null)
                throw new ArgumentNullException("name");

            if (name == string.Empty)
                throw new ArgumentException(Messages.Name_must_not_be_empty, "name");

            lock (LockObject)
                return InternalGetPlug(extensionObject, name);
        }

        public Slot GetSlot(object extensionObject, string name)
        {
            if (extensionObject == null)
                throw new ArgumentNullException("extensionObject");

            if (name == null)
                throw new ArgumentNullException("name");

            if (name == string.Empty)
                throw new ArgumentException(Messages.Name_must_not_be_empty, "name");

            lock (LockObject)
                return InternalGetSlot(extensionObject, name);
        }

        public bool IsDestroyed(Extension extension)
        {
            if (extension == null)
                throw new ArgumentNullException("extension");

            lock (LockObject)
                return InternalIsDestroyed(extension);
        }

        public bool IsShared(Extension extension)
        {
            if (extension == null)
                throw new ArgumentNullException("extension");

            lock (LockObject)
                return InternalIsShared(extension);
        }

        public bool IsActivated(Extension extension)
        {
            if (extension == null)
                throw new ArgumentNullException("extension");

            lock (LockObject)
                return InternalIsActivated(extension);
        }

        public bool IsOpen(Slot slot)
        {
            if (slot == null)
                throw new ArgumentNullException("slot");

            lock (LockObject)
                return InternalIsOpen(slot);
        }

        public bool IsPlugged(Slot slot, Plug plug)
        {
            if (slot == null)
                throw new ArgumentNullException("slot");

            if (plug == null)
                throw new ArgumentNullException("plug");

            lock (LockObject)
                return InternalIsPlugged(slot, plug);
        }

        public bool IsSelected(Slot slot, Plug plug)
        {
            if (slot == null)
                throw new ArgumentNullException("slot");

            if (plug == null)
                throw new ArgumentNullException("plug");

            lock (LockObject)
                return InternalIsSelected(slot, plug);
        }

        internal MetaElementCollection<Extension> InternalGetExtensions(string name)
        {
            return _extensionDictionary.ContainsKey(name)
                ? _extensionDictionary[name]
                : new MetaElementCollection<Extension>();
        }

        internal MetaElementCollection<Plug> InternalGetPlugs(string name)
        {
            return _plugDictionary.ContainsKey(name)
                ? _plugDictionary[name]
                : new MetaElementCollection<Plug>();
        }

        internal MetaElementCollection<Slot> InternalGetSlots(string name)
        {
            return _slotDictionary.ContainsKey(name)
                ? _slotDictionary[name]
                : new MetaElementCollection<Slot>();
        }

        internal MetaElementCollection<Slot> InternalGetSlotsWherePlugged(Plug plug)
        {
            return plug.slotsWherePlugged;
        }

        internal MetaElementCollection<Plug> InternalGetPluggedPlugs(Slot slot)
        {
            return slot.pluggedPlugs;
        }

        internal MetaElementCollection<Slot> InternalGetSlotsWhereSelected(Plug plug)
        {
            return plug.slotsWhereSelected;
        }

        internal MetaElementCollection<Plug> InternalGetSelectedPlugs(Slot slot)
        {
            return slot.selectedPlugs;
        }

        internal MetaElementCollection<Extension> InternalGetExtensions(ExtensionType extensionType)
        {
            return extensionType.extensions;
        }

        internal Extension InternalGetSharedExtension(ExtensionType extensionType)
        {
            return extensionType.sharedExtension;
        }

        internal Extension InternalGetExtension(object extensionObject)
        {
            return _extensionObjectDictionary.ContainsKey(extensionObject)
                ? _extensionObjectDictionary[extensionObject]
                : null;
        }

        internal object InternalGetExtensionObject(Extension extension)
        {
            return extension.extensionObject;
        }

        internal Plug InternalGetPlug(object extensionObject, string name)
        {
            return _extensionObjectDictionary.ContainsKey(extensionObject)
                ? _extensionObjectDictionary[extensionObject].Plugs[name]
                : null;
        }

        internal Slot InternalGetSlot(object extensionObject, string name)
        {
            return _extensionObjectDictionary.ContainsKey(extensionObject)
                ? _extensionObjectDictionary[extensionObject].Slots[name]
                : null;
        }

        internal bool InternalIsDestroyed(Extension extension)
        {
            return !extension.ExtensionType.extensions.Contains(extension);
        }

        internal bool InternalIsShared(Extension extension)
        {
            return extension.ExtensionType.sharedExtension == extension;
        }

        internal bool InternalIsActivated(Extension extension)
        {
            return extension.extensionObject != null;
        }

        internal bool InternalIsOpen(Slot slot)
        {
            return slot.isOpen;
        }

        internal bool InternalIsPlugged(Slot slot, Plug plug)
        {
            return plug.slotsWherePlugged.Contains(slot);
        }

        internal bool InternalIsSelected(Slot slot, Plug plug)
        {
            return plug.slotsWhereSelected.Contains(slot);
        }

        public class InstanceStoreModifier : IInstanceStoreModifier
        {
            private readonly InstanceStore _store;

            public InstanceStoreModifier(InstanceStore store)
            {
                _store = store;
            }

            public void AddExtension(Extension extension)
            {
                if (extension == null)
                    throw new ArgumentNullException("extension");

                lock (_store.LockObject)
                {
                    if (_store.InternalGetExtensions(extension.ExtensionType).Contains(extension))
                        throw new InvalidOperationException("Extension is already added");

                    InternalAddExtension(extension);
                }
            }

            public void RemoveExtension(Extension extension)
            {
                if (extension == null)
                    throw new ArgumentNullException("extension");

                lock (_store.LockObject)
                {
                    if (!_store.InternalGetExtensions(extension.ExtensionType).Contains(extension))
                        throw new InvalidOperationException("Extension is not added");
                    
                    InternalRemoveExtension(extension);
                }
            }

            public void SetSharedExtension(Extension extension)
            {
                if (extension == null)
                    throw new ArgumentNullException("extension");

                lock (_store.LockObject)
                    InternalSetSharedExtension(extension);
            }

            public void AddActivated(Extension extension, object extensionObject)
            {
                if (extension == null)
                    throw new ArgumentNullException("extension");

                if (extensionObject == null)
                    throw new ArgumentNullException("extensionObject");

                lock (_store.LockObject)
                {
                    if (_store.InternalGetExtensionObject(extension) != null)
                        throw new InvalidOperationException("Extension object is already added");
                    
                    InternalAddActivated(extension, extensionObject);
                }
            }

            public void RemoveActivated(Extension extension)
            {
                if (extension == null)
                    throw new ArgumentNullException("extension");

                lock (_store.LockObject)
                {
                    if (_store.InternalGetExtensionObject(extension) == null)
                        throw new InvalidOperationException("Extension object is not added");

                    InternalRemoveActivated(extension);
                }
            }

            public void AddOpened(Slot slot)
            {
                if (slot == null)
                    throw new ArgumentNullException("slot");

                lock (_store.LockObject)
                {
                    if (_store.InternalIsOpen(slot))
                        throw new InvalidOperationException("Slot is already open");
                    
                    InternalAddOpened(slot);
                }
            }

            public void RemoveOpened(Slot slot)
            {
                if (slot == null)
                    throw new ArgumentNullException("slot");

                lock (_store.LockObject)
                {
                    if (!_store.InternalIsOpen(slot))
                        throw new InvalidOperationException("Slot is not open");
                    
                    InternalRemoveOpened(slot);
                }
            }

            public void AddPlugged(Slot slot, Plug plug)
            {
                if (slot == null)
                    throw new ArgumentNullException("slot");

                if (plug == null)
                    throw new ArgumentNullException("plug");

                lock (_store.LockObject)
                {
                    if (_store.InternalIsPlugged(slot, plug))
                        throw new InvalidOperationException("Slot and Plug are already plugged");
                    
                    InternalAddPlugged(slot, plug);
                }
            }

            public void RemovePlugged(Slot slot, Plug plug)
            {
                if (slot == null)
                    throw new ArgumentNullException("slot");

                if (plug == null)
                    throw new ArgumentNullException("plug");

                lock (_store.LockObject)
                {
                    if (!_store.InternalIsPlugged(slot, plug))
                        throw new InvalidOperationException("Slot and Plug are not plugged");
                    
                    InternalRemovePlugged(slot, plug);
                }
            }

            public void AddSelected(Slot slot, Plug plug)
            {
                if (slot == null)
                    throw new ArgumentNullException("slot");

                if (plug == null)
                    throw new ArgumentNullException("plug");

                lock (_store.LockObject)
                {
                    if (_store.InternalIsSelected(slot, plug))
                        throw new InvalidOperationException("Slot and Plug are already selected");
                    
                    InternalAddSelected(slot, plug);
                }
            }

            public void RemoveSelected(Slot slot, Plug plug)
            {
                if (slot == null)
                    throw new ArgumentNullException("slot");

                if (plug == null)
                    throw new ArgumentNullException("plug");

                lock (_store.LockObject)
                {
                    if (_store.InternalIsSelected(slot, plug))
                        throw new InvalidOperationException("Slot and Plug are not selected");
                    
                    InternalRemoveSelected(slot, plug);
                }
            }

            internal void InternalAddExtension(Extension extension)
            {
                extension.ExtensionType.extensions.Add(extension);
                
                (_store._extensionDictionary.ContainsKey(extension.Name)
                    ? _store._extensionDictionary[extension.Name]
                    : (_store._extensionDictionary[extension.Name] =
                        new MetaElementCollection<Extension>())).Add(extension);
                
                foreach (var plug in extension.Plugs)
                    (_store._plugDictionary.ContainsKey(plug.Name)
                        ? _store._plugDictionary[plug.Name]
                        : (_store._plugDictionary[plug.Name] =
                            new MetaElementCollection<Plug>())).Add(plug);
                
                foreach (var slot in extension.Slots)
                    (_store._slotDictionary.ContainsKey(slot.Name)
                        ? _store._slotDictionary[slot.Name]
                        : (_store._slotDictionary[slot.Name] =
                            new MetaElementCollection<Slot>())).Add(slot);
            }

            internal void InternalRemoveExtension(Extension extension)
            {
                extension.ExtensionType.extensions.Remove(extension);
                _store._extensionDictionary[extension.Name].Remove(extension);
                
                foreach (var plug in extension.Plugs)
                    _store._plugDictionary[plug.Name].Remove(plug);
                
                foreach (var slot in extension.Slots)
                    _store._slotDictionary[slot.Name].Remove(slot);
            }

            internal void InternalSetSharedExtension(Extension extension)
            {
                extension.ExtensionType.sharedExtension = extension;
            }

            internal void InternalAddActivated(Extension extension, object extensionObject)
            {
                extension.extensionObject = extensionObject;
                _store._extensionObjectDictionary.Add(extensionObject, extension);
            }

            internal void InternalRemoveActivated(Extension extension)
            {
                _store._extensionObjectDictionary.Remove(extension.extensionObject);
                extension.extensionObject = null;
            }

            internal void InternalAddOpened(Slot slot)
            {
                slot.isOpen = true;
            }

            internal void InternalRemoveOpened(Slot slot)
            {
                slot.isOpen = false;
            }

            internal void InternalAddPlugged(Slot slot, Plug plug)
            {
                slot.pluggedPlugs.Add(plug);
                plug.slotsWherePlugged.Add(slot);
            }

            internal void InternalRemovePlugged(Slot slot, Plug plug)
            {
                slot.pluggedPlugs.Remove(plug);
                plug.slotsWherePlugged.Remove(slot);
            }

            internal void InternalAddSelected(Slot slot, Plug plug)
            {
                slot.selectedPlugs.Add(plug);
                plug.slotsWhereSelected.Add(slot);
            }

            internal void InternalRemoveSelected(Slot slot, Plug plug)
            {
                slot.selectedPlugs.Remove(plug);
                plug.slotsWhereSelected.Remove(slot);
            }
        }
    }
}

